/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#include "filewatcher.h"
#include "nodesmodel.h"
#include "attributesmodel.h"
#include "memorynode.h"
#include "attributevalidator.h"
#include "attributesmodel.h"

#include <QDebug>
#include <QFileInfo>


FileWatcher::FileWatcher(QObject* parent): QObject(parent), _watcher(new QFileSystemWatcher(this))
{
    connect(_watcher, &QFileSystemWatcher::fileChanged, this, &FileWatcher::fileChanged);
}

void FileWatcher::watch(const QString& root_file, NodesModel * nodes_model)
{
    // clear watched attributes
    clear();

    changeRootFile(root_file);

    for(const auto& child: nodes_model->getRoot()->getAllChildren())
    {
        const QModelIndex& node_index = nodes_model->getIndex(child);
        for(const auto& attribute: child->getAttributeContainer()->getAllAttributes())
        {
            if(attribute->getValidator()->getAttributeType() == "file")
            {
                const QString attr_val = QDir::toNativeSeparators(QString::fromStdString(attribute->getValue()));
                if(QDir::isAbsolutePath(attr_val))
                {
                    AttributesModel * attributes_model = nodes_model->getAttributesModel(node_index);
                    const QModelIndex& attribute_index = attributes_model->getIndex(attribute);

                    _watcher->addPath(attr_val);
                    _watched_attributes.insert(attr_val, attribute_index);
                }
            }
        }
    }
}

void FileWatcher::ignoreNextSignalFromFile(const QString &file)
{
    const QString& nativeFilePath = QDir::toNativeSeparators(file);
    if(_watcher->files().contains(nativeFilePath)) // file is monitored
    {
        // insert/update the value with the current time
        temp_blocked_files[nativeFilePath] = QTime::currentTime();
    }
}

void FileWatcher::clear()
{
    auto dirs = _watcher->directories();
    auto files = _watcher->files();
    if(!dirs.empty())
        _watcher->removePaths(dirs);
    if(!files.empty())
        _watcher->removePaths(files);
    _watched_attributes.clear();
    _rootFile.clear();
}

void FileWatcher::fileChanged(const QString &path)
{
    const QString& nativeFilePath = QDir::toNativeSeparators(path);
    /* As specified in the QT5 documentation for QFileSystemWatcher::fileChanged,
       checking for new file / delete old / move new file kind of behaviour on
       linux by checking if the call is actually that the file has been deleted.
    */
    if(!_watcher->files().contains(nativeFilePath))
    {
        QFileInfo check_file(nativeFilePath);
        if (check_file.exists() && check_file.isFile())
        {
            _watcher->addPath(nativeFilePath);
        }
    }
    if(temp_blocked_files.contains(nativeFilePath))
    {
        if(temp_blocked_files.value(nativeFilePath).msecsTo(QTime::currentTime()) < block_timeout_msec)
        {
            return;
        }
        else
        {
            temp_blocked_files.remove(nativeFilePath);
        }
    }
    else
    {
        temp_blocked_files.insert(nativeFilePath, QTime::currentTime());
    }
    if(nativeFilePath == _rootFile)
    {
        Q_EMIT rootChanged();
    }
    else
    {
        for(const auto& index: _watched_attributes.values(nativeFilePath))
        {
            Q_EMIT attributesFileChanged(index);
        }
    }
}

void FileWatcher::attributeInserted(AttributesModel* model, const QModelIndex &parent, int first, int last)
{
    for(int row = first; row <= last; ++row)
    {
        const QModelIndex& index = model->index(row, 0, parent);
        if(model->isAttribute(index))
        {
            attributeChanged(index); // will add
        }
    }
}

void FileWatcher::attributeChanged(const QModelIndex &index)
{
    AttributesModel * model = getAttributesModel(index);
    Attribute* attribute = model->getAttribute(index);
    if(attribute->getValidator()->getAttributeType() == "file")
    {
        _watched_attributes.remove(_watched_attributes.key(index), index);
        const QString attr_val = QDir::toNativeSeparators(QString::fromStdString(attribute->getValue()));
        if(QDir::isAbsolutePath(attr_val))
        {
            _watched_attributes.insert(attr_val, index);
        }
    }
}

void FileWatcher::changeRootFile(const QString &root_file)
{
    const QString nativeRootPath = QDir::toNativeSeparators(root_file);
    if(!_rootFile.isEmpty())
        _watcher->removePath(_rootFile);
    _rootFile = nativeRootPath;
    _watcher->addPath(_rootFile);
    _rootDir.setPath(QFileInfo(nativeRootPath).absolutePath());
}

AttributesModel *FileWatcher::getAttributesModel(const QModelIndex &index) const
{
    return static_cast<AttributesModel*>(const_cast<QAbstractItemModel*>(index.model()));
}
